/*
 *    Factory: Spawn objects of classes that were previously
 *    registered by their base class.  Classes of a common base class X are
 *    stored together so that a random X can be spawned (the whole basis for
 *    it).
 *
 *    Copyright (C) 2012  Jeremy W. Murphy <jeremy.william.murphy@gmail.com>
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// Thanks to teotwaki from ##c++ on Freenode for doing the original hard work.

#ifndef FACTORY
#define FACTORY

#include <boost/functional/factory.hpp>
#include <boost/function.hpp>
#include <vector>
#include <stdexcept>


template <typename AbstractProduct, typename... Parameters>
class Factory
{
	typedef boost::function<AbstractProduct * (Parameters... parameters)> factory_type;
	
public:
    Factory() {}

    template <class ConcreteProduct>
    /**
     * @brief Register a <ConcreteProduct> factory.
     *
	 * @return size_t
     **/
    size_t registersubclass()
    {
        factory.push_back(boost::factory<ConcreteProduct *>());
		return factory.size() - 1; // Report which index it is.
    }


    /**
	 * @brief Unregister a <ConcreteProduct> factory.
	 *
	 * @return void
	 **/
	void unregistersubclass(size_t const &INDEX)
	{
		auto i(std::begin(factory));
		std::advance(i, INDEX);
		factory.erase(i);
	}

    /**
     * @brief Instantiate a new object from factory[i].
     *
     * @param i Factory index.  Use size() to get limit.
     * @return : A Generalization pointer to the new object.
     **/
    // TODO: Does unique_ptr work with the polymorphism required by the clients?  (I think it does.)
    std::unique_ptr<AbstractProduct> spawn(unsigned const &i, Parameters... parameters) const
    {
        // TODO: Implement error-handling policy class.
		return std::unique_ptr<AbstractProduct>(factory[i](parameters...));
    }


    /**
     * @brief Return size of factory vector (number of classes).  Clients need to know this in order to generate random indexes within the correct bound.
     *
     * @return :size_t
     **/
    std::size_t size() const
    {
        return factory.size();
    }

private:
	std::vector<factory_type> factory;
};

#endif
